﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Noris.Tools.TraceVisualiser.Support;
using Noris.Tools.TraceVisualiser.Components;
using Djs.Tools.XmlPersistor;

namespace Noris.Tools.TraceVisualiser.TraceData
{
    /// <summary>
    /// TraceDocument : hlavička trace projektu.
    /// Obsahuje jeho vlastnosti, zajišťuje jejich persistenci do souboru.
    /// Neobsahuje datový zdroj ani graf.
    /// S datovým zdrojem a s grafem se setkává až na containeru TracePanel.
    /// </summary>
    public class TraceDocument : IDisposable
    {
        #region Konstrukce, property
        public TraceDocument()
        {
            this._Init(false);
        }
        public TraceDocument(bool fillDefault)
        {
            this._Init(fillDefault);
        }
		public TraceDocument(string projectFile)
		{
			this._Init(false);
			this.Load(projectFile);
		}

		/// <summary>
        /// Disposable
        /// </summary>
        public void Dispose()
        {
            if (this.DataSource != null)
                this.DataSource.Dispose();
        }
        /// <summary>
        /// Název projektu = uživatelský
        /// </summary>
        public string DocumentName { get; set; }
        /// <summary>
        /// Soubor s uloženým projektem
        /// </summary>
        [PersistingEnabled(false)]
        public string ProjectFile { get; set; }
        /// <summary>
        /// Režim vstupních trace souborů (jeden / adresář / ruční výběr)
        /// </summary>
        public TraceFileRangeType TraceFileRange { get; set; }
        /// <summary>
        /// Plná cesta k souboru / adresář souborů
        /// </summary>
        /// <remarks>
        /// Tato property se do XML neukládá přímo, ale prostřednictvím property RelativeTracePath
        /// </remarks>
        [PersistingEnabled(false)]
        public string TracePath { get; set; }
        /// <summary>
        /// Explicitně vyjmenované soubory, plná cesta
        /// </summary>
        /// <remarks>
        /// Tato property se do XML neukládá přímo, ale prostřednictvím property RelativeExplicitTraceFiles
        /// </remarks>
        [PersistingEnabled(false)]
        public List<string> ExplicitTraceFiles { get; set; }
        /// <summary>
        /// Umožnit načítání dat předem v době, kdy se nic nenačítá
        /// (vhodné pro menší projekty)
        /// </summary>
        public bool PreLoadFiles { get; set; }
        /// <summary>
        /// Hlídat změny souborů v adresáři (uplatní se pouze pro režim TraceFileRange == OneDirectory nebo AllSubdirectories, kdy lze hlídat celý adresář, a OneFile kdy lze hlídat změnu obsahu)
        /// (vhodné pro menší projekty)
        /// </summary>
        public bool WatchTraceChanges { get; set; }
        /// <summary>
        /// Přednastavená úroveň detailů zobrazovaných v trace visualiseru
        /// </summary>
        public int PresetDetailLevel { get; set; }
        /// <summary>
        /// Filtry, podle nichž se budou načítat položky Trace.
        /// Zde je uložen seznam klíčových slov filtrů, oddělených středníkem.
        /// </summary>
        public string Filter { get { return this._Filter; } set { _SetFilter(value); } }
        /// <summary>
        /// Příznak, že soubor s projektem je určen
        /// </summary>
        [PersistingEnabled(false)]
        public bool HasProjectFile { get { return (!this.ProjectFile.IsEmpty()); } }
        /// <summary>
        /// Příznak, že soubor s projektem je načten do tohoto objektu
        /// </summary>
        [PersistingEnabled(false)]
        public bool ProjectFileLoaded { get; protected set; }
        /// <summary>
		/// Příznak, že tento objekt je uložen do souboru projektu
        /// </summary>
        [PersistingEnabled(false)]
		public bool ProjectFileSaved { get; protected set; }
	    /// <summary>
        /// Vzhled grafu (sloupce, záhlaví)
        /// </summary>
        internal DocumentGraphLayout DocumentLayout { get; set; }
        /// <summary>
        /// Vrátí adresář, v němž se mají sledovat změny souborů.
        /// Vrátí null, pokud se nemá sledovat nic.
        /// </summary>
        /// <returns></returns>
        internal string GetWatchDirectory()
        {
            if (!this.WatchTraceChanges) return null;
            if (String.IsNullOrEmpty(this.TracePath)) return null;

            if (this.TraceFileRange == TraceFileRangeType.OneFile || this.TraceFileRange == TraceFileRangeType.OneDirectory)
                return System.IO.Path.GetDirectoryName(this.TracePath);

            return null;
        }
        #endregion
        #region Property, které se ukládají do XML souboru a obsahují relativní cesty k souborům, namísto absolutních cest
        /// <summary>
        /// Relativní cesta k souboru / adresář souborů
        /// </summary>
        /// <remarks>
        /// Tato property se ukládá do XML, umožní se tím přenositelnost projektu a dat
        /// </remarks>
        [PropertyName("TracePath")]
        protected string RelativeTracePath { get; set; }
        /// <summary>
        /// Explicitně vyjmenované soubory, relativní cesta
        /// </summary>
        /// <remarks>
        /// Tato property se ukládá do XML, umožní se tím přenositelnost projektu a dat
        /// </remarks>
        [PropertyName("ExplicitTraceFiles")]
        protected List<string> RelativeExplicitTraceFiles { get; set; }
        /// <summary>
        /// Adresář, v němž byl projekt projekt ukládán, a k němuž jsou určovány relativní cesty k souborům do property RelativeTracePath a RelativeExplicitTraceFiles.
        /// </summary>
        protected string OriginalBasePath { get; set; }
        /// <summary>
        /// Metoda připraví data do property pro relativní soubory/adresáře: OriginalBasePath, RelativeTracePath, RelativeExplicitTraceFiles.
        /// Volá se před uložením dat z objektu do XML souboru.
        /// </summary>
        protected void PrepareToSave()
        {
            this.RelativeTracePath = "";
            this.RelativeExplicitTraceFiles = null;
            this.OriginalBasePath = System.IO.Path.GetDirectoryName(this.ProjectFile) + "\\";
            System.Uri baseUri = new Uri(this.OriginalBasePath);

            // TracePath
            if (!String.IsNullOrEmpty(this.TracePath))
            {
                this.RelativeTracePath = GetRelativePath(baseUri, this.TracePath);
            }

            // ExplicitTraceFiles
            if (this.ExplicitTraceFiles != null)
            {
                this.RelativeExplicitTraceFiles = new List<string>();
                foreach (string absoluteFile in this.ExplicitTraceFiles)
                {
                    if (!String.IsNullOrEmpty(absoluteFile))
                    {
                        string relativeFile = GetRelativePath(baseUri, absoluteFile);
                        this.RelativeExplicitTraceFiles.Add(relativeFile);
                    }
                }
                if (this.RelativeExplicitTraceFiles.Count == 0)
                    this.RelativeExplicitTraceFiles = null;
            }
        }
        /// <summary>
        /// Metoda připraví data do property pro absolutní soubory/adresáře: TracePath, ExplicitTraceFiles na základě relativních cest uložených v XML souboru.
        /// Volá se po načtení dat z XML souboru do objektu.
        /// </summary>
        protected void FinishLoading()
        {
            // Nejprve načteme data bez úprav:
            this.TracePath = this.RelativeTracePath;
            this.ExplicitTraceFiles = new List<string>();

            // Pokud jsem načetl projekt, který obsahuje absolutní cesty a nemá uvedenu základní cestu OriginalBasePath, pak jde o starší typ projektu a máme hotovo:
            if (String.IsNullOrEmpty(this.OriginalBasePath))
                return;

            System.Uri originalBaseUri = new Uri(this.OriginalBasePath);
            System.Uri currentBaseUri = new Uri(System.IO.Path.GetDirectoryName(this.ProjectFile) + "\\");

            // TracePath
            if (!String.IsNullOrEmpty(this.RelativeTracePath))
            {
                bool asFile = (this.TraceFileRange != TraceFileRangeType.AllSubdirectories);
                this.TracePath = GetAbsolutePath(originalBaseUri, currentBaseUri, this.RelativeTracePath, true, asFile);
            }

            // ExplicitTraceFiles
            if (this.RelativeExplicitTraceFiles != null)
            {
                foreach (string relativeFile in this.RelativeExplicitTraceFiles)
                {
                    if (!String.IsNullOrEmpty(relativeFile))
                    {
                        string absoluteFile = GetAbsolutePath(originalBaseUri, currentBaseUri, relativeFile, true, true);
                        if (absoluteFile != null)
                            this.ExplicitTraceFiles.Add(absoluteFile);
                    }
                }
            }
        }
        /// <summary>
        /// Vrátí relativní cestu v rámci základny (baseUri) k danému umístění (soubor/adresář).
        /// </summary>
        /// <param name="baseUri"></param>
        /// <param name="absolutePath"></param>
        /// <returns></returns>
        internal static string GetRelativePath(Uri baseUri, string absolutePath)
        {
            string relativePath = absolutePath;
            Uri absoluteUri;
            if (Uri.TryCreate(absolutePath, UriKind.RelativeOrAbsolute, out absoluteUri))
            {
                if (absoluteUri.IsAbsoluteUri)
                {
                    Uri relativeUri = baseUri.MakeRelativeUri(absoluteUri);
                    relativePath = Uri.UnescapeDataString(relativeUri.OriginalString);
                }
            }
            return relativePath;
        }
        /// <summary>
        /// Vrátí absolutní cestu k danému souboru v rámci původní nebo nové základy (řeší přemístění projektu a/nebo datových souborů).
        /// </summary>
        /// <param name="originalBaseUri"></param>
        /// <param name="currentBaseUri"></param>
        /// <param name="relativePath"></param>
        /// <param name="checkExists"></param>
        /// <param name="asFile"></param>
        /// <returns></returns>
        internal static string GetAbsolutePath(Uri originalBaseUri, Uri currentBaseUri, string relativePath, bool checkExists, bool asFile)
        {
            string absolutePath = null;

            absolutePath = GetAbsolutePath(originalBaseUri, relativePath, checkExists, asFile);
            
            if (absolutePath == null)
                absolutePath = GetAbsolutePath(currentBaseUri, relativePath, checkExists, asFile);

            if (absolutePath == null)
                absolutePath = relativePath;

            return absolutePath;
        }
        /// <summary>
        /// Vrátí absolutní cestu k danému souboru v rámci původní nebo nové základy (řeší přemístění projektu a/nebo datových souborů).
        /// </summary>
        /// <param name="baseUri">Základna</param>
        /// <param name="relativePath">Relativní umístění souboru</param>
        /// <param name="checkExists"></param>
        /// <param name="asFile"></param>
        /// <returns></returns>
        internal static string GetAbsolutePath(Uri baseUri, string relativePath, bool checkExists, bool asFile)
        {
            Uri absoluteUri;
            if (Uri.TryCreate(baseUri, relativePath, out absoluteUri))
            {
                if (absoluteUri.IsAbsoluteUri)
                {
                    string localPath = absoluteUri.LocalPath;
                    if (!checkExists) return localPath;

                    if (asFile && System.IO.File.Exists(localPath))
                        return localPath;
                    else if (!asFile && System.IO.Directory.Exists(localPath))
                        return localPath;
                }
            }
            return null;
        }
        #endregion
        #region Správa konfigurace
        internal void PropertiesChanged()
        {
            this.SaveIfCan();
            if (this.DataSource != null && this.DataSource.Main != null)
                this.DataSource.Main.DocumentChanged();
        }
        #endregion
        #region Vytváření nového dokumentu pro explicitně daný zdroj (CSV, adresář, projekt HEGTA, HEGTV)
        /// <summary>
        /// Vytvoří a vrátí Trace dokument pro specifikovaný HEGTV soubor
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static TraceDocument CreateForHegTVFile(string fileName)
        {
            TraceDocument doc = new TraceDocument();
            doc._Init(true);
            doc.Load(fileName);
            return doc;
        }
        /// <summary>
        /// Vytvoří a vrátí Trace dokument pro specifikovaný CSV soubor
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static TraceDocument CreateForCsvFile(string fileName)
        {
            TraceDocument doc = new TraceDocument();
            doc._Init(true);
            doc.DocumentName = CreateProjectName(fileName);
            doc.ProjectFile = CreateProjectFile(fileName);
            if (System.IO.File.Exists(doc.ProjectFile))
            {
                doc.Load(doc.ProjectFile);
                doc.TraceFileRange = TraceFileRangeType.OneFile;
                doc.TracePath = fileName;
            }
            else
            {
                doc.TraceFileRange = TraceFileRangeType.OneFile;
                doc.TracePath = fileName;
                doc.PreLoadFiles = false;
                doc.WatchTraceChanges = false;
                doc._InitPreset(PRESET_3);
            }
            return doc;
        }
        /// <summary>
        /// Vytvoří a vrátí Trace dokument pro specifikovaný soubor TraceAnalyzátoru (HEGTA)
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static TraceDocument CreateForHegTAFile(string fileName)
        {
            TraceDocument doc = new TraceDocument();
            doc._Init(true);
            doc.DocumentName = CreateProjectName(fileName);
            doc.ProjectFile = CreateProjectFile(fileName);
            if (System.IO.File.Exists(doc.ProjectFile))
            {
                doc.Load(doc.ProjectFile);
            }

            // Data z TA mají přednost => znovu z HEGTA načtu seznam vstupních souborů:
            doc.TraceFileRange = TraceFileRangeType.ExplicitFiles;
            doc.TracePath = "";
            doc.ExplicitTraceFiles = new List<string>();

            // Z projektu TraceAnalyzeru převezmu seznam souborů (pokud to jen trochu půjde):
            try
            {
                var taProj = TraceAnalyzer.Engine.Projects.Project.Open(fileName);
                foreach (var input in taProj.Inputs)
                {
                    if (input.TraceKind == TraceAnalyzer.Extensibility.KnownTraceKind.ServerTrace ||
                        input.TraceKind == TraceAnalyzer.Extensibility.KnownTraceKind.ClientTrace ||
                        input.TraceKind == TraceAnalyzer.Extensibility.KnownTraceKind.ServerTraceBlocking ||
                        input.TraceKind == TraceAnalyzer.Extensibility.KnownTraceKind.ServerTraceThread)
                    {   //  TraceAnalyzer detekoval daný soubor jako pro nás známý typ, převezmu si jej:
                        doc.ExplicitTraceFiles.Add(input.FileName);
                    }
                }
            }
            catch (Exception exc)
            {
                Steward.Audit( AuditRowType.CoreError, "Chyba při načítání dat z projektu HEGTA", exc);
            }

            if (!doc.ProjectFileLoaded)
            {   // Parametry => jen při prvním vytváření HEGTV:
                doc.PreLoadFiles = false;
                doc.WatchTraceChanges = false;
                doc._InitPresetOptimal();
            }
            return doc;
        }
        ///// <summary>
        ///// Vytvoří a vrátí jméno souboru projektu na základě daného jména vstupního souboru.
        ///// </summary>
        ///// <param name="fileName"></param>
        ///// <returns></returns>
        //public static string CreateProjectName(string fileName, bool unique)
        //{
        //    string projName = null;
        //    return CreateProjectName(fileName, unique, ref projName);
        //}
        ///// <summary>
        ///// Vytvoří a vrátí jméno souboru projektu na základě daného jména vstupního souboru.
        ///// </summary>
        ///// <param name="fileName"></param>
        ///// <returns></returns>
        //public static string CreateProjectName(string fileName, bool unique, ref string projName)
        //{
        //    string nameBase = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(fileName), System.IO.Path.GetFileNameWithoutExtension(fileName));       // "C:\Dokumenty\Trace\Soubor"
        //    string nameExte = "." + PROJECT_EXTENSION;
        //    if (!unique) return nameBase + nameExte;                                    // "C:\Dokumenty\Trace\Soubor.hegtv"
        //    for (int i = 0; i < 1000; i++)
        //    {
        //        string suffix = (i == 0 ? "" : "~" + i.ToString("000"));
        //        string nameTest = nameBase + suffix + nameExte;        // "C:\Dokumenty\Trace\Soubor~001.hegtv"
        //        if (!System.IO.File.Exists(nameTest))
        //        {
        //            if (projName != null)
        //                projName = projName + suffix;
        //            return nameTest;
        //        }
        //    }
        //    return nameBase + nameExte;
        //}

        public static void CreateProjectFileName(string inputPath, CreateProjectNameMode nameMode, bool uniqueName, ref string projectFile, ref string projectName)
        {
            System.IO.FileInfo fi;
            System.IO.DirectoryInfo di;
            switch (nameMode)
            {
                case CreateProjectNameMode.FullNameCopy:
                    fi = new System.IO.FileInfo(inputPath);
                    projectFile = TraceDocument.CreateProjectFile(fi.Directory, fi.Name);
                    projectName = TraceDocument.CreateProjectName(fi.Directory, fi.Name);
                    break;
                case CreateProjectNameMode.DirectoryFromName:
                    fi = new System.IO.FileInfo(inputPath);
                    projectFile = TraceDocument.CreateProjectFile(fi.Directory, null);
                    projectName = TraceDocument.CreateProjectName(fi.Directory, null);
                    break;
                case CreateProjectNameMode.Directory:
                    di = new System.IO.DirectoryInfo(inputPath);
                    projectFile = TraceDocument.CreateProjectFile(di, null);
                    projectName = TraceDocument.CreateProjectName(di, null);
                    break;
            }
            if (uniqueName && !String.IsNullOrEmpty(projectFile))
            {
                // projectFile = TraceDocument.CreateProjectName(projectFile, uniqueName, ref projectName);
            }
        }

        public static string CreateProjectFile(string dirName, string fileName)
        {
            return CreateProjectFile(new System.IO.DirectoryInfo(dirName), fileName);
        }
        public static string CreateProjectFile(string fileName)
        {
            System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
            return CreateProjectFile(fileInfo.Directory, fileInfo.Name);
        }
        public static string CreateProjectFile(System.IO.DirectoryInfo dirInfo, string fileName)
        {
            return System.IO.Path.Combine(dirInfo.FullName, CreateProjectName(dirInfo, fileName) + "." + TraceDocument.PROJECT_EXTENSION);
        }
        public static string CreateProjectName(string dirName, string fileName)
        {
            return CreateProjectName(new System.IO.DirectoryInfo(dirName), fileName);
        }
        public static string CreateProjectName(string fileName)
        {
            System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
            return CreateProjectName(fileInfo.Directory, fileInfo.Name);
        }
        public static string CreateProjectName(System.IO.DirectoryInfo dirInfo, string fileName)
        {
            string name = "";
            int cnt = 0;
            System.IO.DirectoryInfo di = dirInfo;
            if (fileName != null)
                di = new System.IO.DirectoryInfo(System.IO.Path.Combine(di.FullName, System.IO.Path.GetFileNameWithoutExtension(fileName)));

            while (cnt < 2 && di != null)
            {   // Jméno bude vypadat jako jména UP adresářů (v jedné řadě), složky oddělené "-", budou tam dvě složky s názvem delším (nebo rovným) než 3 znaky, kratší složky tam budou taky ale nepočítají se:
                // Takže z adresáře "C:\DavidPrac\TraceVisualiser\Kordarna\2012-10-18\B\" bude vytvořeno jméno "Kordarna-2012-10-18-B", 
                //  z adresáře "C:\DavidPrac\TraceVisualiser\Brano\2012-10-12b\" bude "Brano-2012-10-12b", atd.
                name = di.Name + (name.Length == 0 ? "" : "-") + name;
                if (di.Name.Length >= 3)
                    cnt++;
                di = di.Parent;
            }
            return name;
        }
        public enum CreateProjectNameMode { None, FullNameCopy, DirectoryFromName, Directory }
        #endregion
        #region Pointery na specifická místa v projektu (čas, řádek)
        /// <summary>
        /// Aktuální místo zájmu (pointer). Hodnotu sem ukládá vizuální vrstva. Není třeba ji persistovat.
        /// </summary>
        [PersistingEnabled(false)]
        public IGraphPointer CurrentDocPointer { get; set; }
        /// <summary>
        /// Seznam pointerů na místa v grafu
        /// </summary>
        public List<TraceDocPointer> DocPointers { get; set; }
        /// <summary>
        /// Metoda založí nový pointer, může se zeptat na jeho název, uloží si jej do své evidence (this.DocPointers), a vrátí jej.
		/// Může vrátit null.
        /// </summary>
		/// <param name="withName">Včetně pojmenování</param>
        /// <returns></returns>
		internal IGraphPointer GetNewDocPointer(bool withName)
        {
            TraceDocPointer docPointer = new TraceDocPointer();
            docPointer.Name = "Bod zájmu číslo " + (this.DocPointers.Count + 1).ToString();

            // Zde je možno pojmenovat pointer:
			if (withName)
			{
				using (Forms.GetTextDialogForm getTextForm = new Forms.GetTextDialogForm())
				{
					getTextForm.LabelText = "Název pozice:";
					getTextForm.TextboxText = docPointer.Name;
					System.Windows.Forms.DialogResult result = getTextForm.ShowDialog(Steward.MainForm);
					if (result != System.Windows.Forms.DialogResult.OK)
						return null;
					string name = getTextForm.TextboxText;
					if (name.IsEmpty())
						return null;

					docPointer.Name = name;
				}
			}

            this.DocPointers.Add(docPointer);
            this.CurrentDocPointer = docPointer;
            // this.DocPointerChanged();               // Nelze provádět zde, protože pointer ještě není naplněn daty, to provede až vyšší metoda, a ta také ukládá dokument.
            return docPointer;
        }
		/// <summary>
		/// Metoda přejmenuje daný pointer
		/// </summary>
		/// <param name="graphPointer"></param>
		/// <returns></returns>
		internal bool RenameDocPointer(IGraphPointer graphPointer)
		{
			TraceDocPointer inpPointer = graphPointer as TraceDocPointer;
			if (inpPointer == null) return false;

			TraceDocPointer docPointer = this.DocPointers.FirstOrDefault(p => Object.ReferenceEquals(p, inpPointer));
			if (docPointer == null) return false;

			using (Forms.GetTextDialogForm getTextForm = new Forms.GetTextDialogForm())
			{
				getTextForm.LabelText = "Název pozice:";
				getTextForm.TextboxText = docPointer.Name;
				System.Windows.Forms.DialogResult result = getTextForm.ShowDialog(Steward.MainForm);
				if (result != System.Windows.Forms.DialogResult.OK)
					return false;
				string name = getTextForm.TextboxText;
				if (name.IsEmpty())
					return false;

				docPointer.Name = name;
			}
            this.DocPointerChanged();
            return true;
		}
        /// <summary>
        /// Metoda odebere daný pointer ze svého seznamu.
        /// </summary>
        /// <param name="graphPointer"></param>
        internal bool DeleteDocPointer(IGraphPointer graphPointer)
        {
            TraceDocPointer docPointer = graphPointer as TraceDocPointer;
            if (docPointer == null) return false;

            if (Object.ReferenceEquals(this.CurrentDocPointer, graphPointer))
                this.CurrentDocPointer = null;

            if (!this.DocPointers.Any(p => Object.ReferenceEquals(p, docPointer)))
                return false;

            int cntb = this.DocPointers.Count;
            this.DocPointers.RemoveAll(p => Object.ReferenceEquals(p, docPointer));
            bool changed = (this.DocPointers.Count < cntb);
            if (!changed) return false;
            
            this.DocPointerChanged();
            return true;
        }
        protected virtual void DocPointerChanged()
        {
            this.SaveAuto();
        }
        #endregion
        #region Persistence dat pro Servery (Časové posuny serverů - pro každé jméno serveru si pamatujeme časový posun pro explicitně zadané časy)
        /// <summary>
        /// Z dokumentu (this) načte data pro Server a vloží je do něj (soupis časových posunů za daný server).
        /// </summary>
        /// <param name="serverInfo"></param>
        public void LoadServerInfo(ServerInfo serverInfo)
        {
            TraceServerInfoData data = this.ServerTimeOffsetList.FirstOrDefault(x => x.ServerName == serverInfo.ServerName);
            if (data == null) return;

            serverInfo.ServerTimeOffsetData = data;
        }
        /// <summary>
        /// Do dokumentu (this) vloží potřebná data ze serveru (soupis časových posunů za daný server).
        /// </summary>
        /// <param name="serverInfo"></param>
        public void SaveServerInfo(ServerInfo serverInfo)
        {
            TraceServerInfoData data = serverInfo.ServerTimeOffsetData;
            int index = this.ServerTimeOffsetList.FindIndex(x => x.ServerName == data.ServerName);

            bool save = false;
            if (index < 0 && !data.ContainData)
            {   // Záznam neexistuje, a ani nebude.
            }
            else if (index >= 0 && !data.ContainData)
            {   // Záznam neexistuje, ale není zapotřebí.
                this.ServerTimeOffsetList.RemoveAt(index);
                save = true;
            }
            else if (index < 0 && data.ContainData)
            {   // Záznam neexistuje, a měl by.
                this.ServerTimeOffsetList.Add(data);
                save = true;
            }
            else if (index >= 0 && data.ContainData)
            {   // Záznam existuje, a měl by, přepíšu jej.
                this.ServerTimeOffsetList[index] = data;
                save = true;
            }

            if (save)
                this.SaveAuto();
        }
        /// <summary>
        /// Seznam serverů a jejich časové posuny
        /// </summary>
        public List<TraceServerInfoData> ServerTimeOffsetList { get; private set; }
        #endregion
        #region Persistence - public
        /// <summary>
		/// Vynuluje obsah dokumentu
		/// </summary>
        public void Reset()
        {
            this._Init(true);
        }
		/// <summary>
		/// Načte do sebe data dokumentu z daného souboru
		/// </summary>
		/// <param name="projectFile"></param>
        public void Load(string projectFile)
        {
            this.ProjectFile = projectFile;
            this._Load();
        }
		/// <summary>
		/// Znovu načte do sebe data dokumentu ze souboru, z něhož byl načten / uložen
		/// </summary>
        public void ReLoad()
        {
            this._Load();
        }
		/// <summary>
		/// Uloží svoje data do svého souboru
		/// </summary>
        public void Save()
        {
            this._Save();
        }
        /// <summary>
        /// Ukládá dokument automaticky po každé změně, pokud je nastaveno v konfiguraci, a pokud dokument má svoje jméno
        /// </summary>
        public void SaveAuto()
        {
            this.SaveIfCan();
        }
		/// <summary>
		/// Ukládá dokument do svého souboru, pokud je to možné (soubor je zadán)
		/// </summary>
		public void SaveIfCan()
		{
			if (this.HasProjectFile)
				this._Save();
		}
		/// <summary>
		/// Uloží svoje data do zadaného souboru
		/// </summary>
		/// <param name="projectFile"></param>
        public void SaveAs(string projectFile)
        {
            this.ProjectFile = projectFile;
            this._Save();
        }
        #endregion
        #region Persistence - private
        private void _Init(bool fillDefault)
        {
            this.DocumentName = (fillDefault ? "Nový projekt" : null);
            this.ProjectFile = null;
            this.TraceFileRange = (fillDefault ? TraceFileRangeType.OneDirectory : TraceFileRangeType.None);
            this.TracePath = null;
            this.PreLoadFiles = false;
            this.WatchTraceChanges = (fillDefault ? true : false);
            this.ExplicitTraceFiles = new List<string>();
            this.DocPointers = new List<TraceDocPointer>();
            this.ServerTimeOffsetList = new List<TraceServerInfoData>();
            this.PresetDetailLevel = 0;
            this.Filter = (fillDefault ? GetFilterForPreset(PRESET_DEFAULT) : "");
            if (fillDefault)
                this._InitPresetDefault();

            this._FilterDict = new Dictionary<string, bool>();
            this.DocumentLayout = new DocumentGraphLayout(fillDefault);
            this.ProjectFileLoaded = false;
            this.Auditlog = new AuditLog();
        }
		private void _Load()
        {
            string fileName = this.ProjectFile;
            if (String.IsNullOrEmpty(fileName)) return;
            if (!System.IO.File.Exists(fileName))
            {
                Dialog.ShowWarning("Zadaný soubor neexistuje." + Environment.NewLine + fileName);
                return;
            }

            PersistArgs args = PersistArgs.Default;
			args.XmlFile = fileName;
            Persist.LoadTo(args, this);
            this.FinishLoading();

			this.ProjectFileLoaded = true;
			this.ProjectFileSaved = true;
        }
        private void _Save()
        {
            string fileName = this.ProjectFile;
            if (String.IsNullOrEmpty(fileName)) return;

            this.PrepareToSave();
			PersistArgs args = PersistArgs.Default;
			args.XmlFile = fileName;
			Persist.Serialize(this, args);

			this.ProjectFileSaved = true;
        }
        #endregion
		#region Otevírání oken
		/// <summary>
		/// Otevře dialog s vlastnostmi projektu, vrátí result dialogu
		/// </summary>
		/// <returns></returns>
		public System.Windows.Forms.DialogResult ShowPropertiesFormDialog()
		{
			using (TraceVisualiser.Forms.DocumentPropertiesForm dpf = new Forms.DocumentPropertiesForm())
			{
				dpf.Document = this;
				System.Windows.Forms.DialogResult result = dpf.ShowDialog();
                if (result == System.Windows.Forms.DialogResult.OK)
                {
                    this.ShowTraceDataFormRefresh();
                    this.PropertiesChanged();
                }
				return result;
			}
		}
        /// <summary>
        /// Otevře dialog s auditlogem projektu, vrátí result dialogu
        /// </summary>
        /// <returns></returns>
        public System.Windows.Forms.DialogResult ShowAuditlogFormDialog()
        {
            this.Auditlog.ShowModal("Auditlog", true);
            return System.Windows.Forms.DialogResult.OK;
        }
		/// <summary>
		/// Otevře formulář s trace grafem projektu.
		/// Formulář otevře jako nemodální (tj. v novém threadu), řízení ihned vrací.
		/// </summary>
		/// <param name="mdiParentForm"></param>
        internal void ShowTraceDataForm(TraceVisualiser.Forms.MainAppForm mainAppForm)
		{
			if (this.GraphForm == null)
			{
				this._GraphForm = new Forms.TraceDocumentForm();
				this.GraphForm.Document = this;
				this.GraphForm.WindowState = System.Windows.Forms.FormWindowState.Maximized;
				this.GraphForm.MainAppForm = mainAppForm;
			}

            if (!Steward.MdiChildShowNew(this.GraphForm))
            {
                this.GraphForm.ShowDialog();
                this.GraphForm.Dispose();
                this._GraphForm = null;
            }
		}
		/// <summary>
		/// Zajistí Refresh okna s grafem tohoto dokumentu
		/// </summary>
		public void ShowTraceDataFormRefresh()
		{
			if (this.GraphForm != null)
			{
				this.GraphForm.Text = this.DocumentName;
			}
		}
		/// <summary>
		/// Formulář s daty dokumentu
		/// </summary>
        [PersistingEnabled(false)]
        public Forms.TraceDocumentForm GraphForm { get { return this._GraphForm; } }
        private Forms.TraceDocumentForm _GraphForm;
        /// <summary>
        /// Zdroj trace dat, komunikuje s grafem, z datového dokumentu dostává zadání (umístění souborů a parametry zpracování).
        /// </summary>
        [PersistingEnabled(false)]
        public TraceDataSource DataSource { get { return (this.GraphForm == null ? null : this.GraphForm.DataSource); } }
		#endregion
        #region Auditlog - sběr informací, zobrazování
        /// <summary>
        /// Auditlog související s tímto dokumentem. Obsah se nepersistuje, po otevření je prázdný. Plní se při načítání souborů.
        /// </summary>
        [PersistingEnabled(false)]
        public AuditLog Auditlog { get; protected set; }
        #endregion
        #region Servisní metody (seznam souborů)
        /// <summary>
        /// Vytvoří seznam vstupních souborů podle aktuálního zadání
        /// </summary>
        /// <returns></returns>
        internal List<string> CreateCurrentFileList()
        {
            return CreateFileList(this.TraceFileRange, this.TracePath, this.ExplicitTraceFiles);
        }
        /// <summary>
        /// Vrátí seznam všech souborů dle zadání
        /// </summary>
        /// <param name="fileRange"></param>
        /// <param name="tracePath"></param>
        /// <param name="explicitFiles"></param>
        /// <returns></returns>
        internal static List<string> CreateFileList(TraceFileRangeType fileRange, string tracePath, List<string> explicitFiles)
        {
            List<string> result = new List<string>();
            switch (fileRange)
            {
                case TraceFileRangeType.OneFile:
                    if (!String.IsNullOrEmpty(tracePath) && System.IO.File.Exists(tracePath))
                        result.Add(tracePath);
                    break;
                case TraceFileRangeType.OneDirectory:
                case TraceFileRangeType.AllSubdirectories:
                    string directory = tracePath;
                    if (!String.IsNullOrEmpty(directory))
                    {
                        // directory může být soubor v adresáři, anebo adresář:
                        if (!System.IO.Directory.Exists(directory) && System.IO.File.Exists(directory))
                            directory = System.IO.Path.GetDirectoryName(directory);
                        if (System.IO.Directory.Exists(directory))
                        {
                            System.IO.SearchOption option = (fileRange == TraceFileRangeType.AllSubdirectories ? System.IO.SearchOption.AllDirectories : System.IO.SearchOption.TopDirectoryOnly);
                            result.AddRange(System.IO.Directory.GetFiles(directory, "*.*", option));
                        }
                    }
                    break;
                case TraceFileRangeType.ExplicitFiles:
                    if (explicitFiles != null)
                    {
                        foreach (string file in explicitFiles)
                        {
                            if (!String.IsNullOrEmpty(file) && System.IO.File.Exists(file))
                                result.Add(file);
                        }
                    }
                    break;
            }
            return result;
        }
        #endregion
        #region Filtrování, konstanty, statické metody pro filtry
        /// <summary>
        /// Metoda určí, zda daný výraz vyhovuje filtru tohoto projektu.
        /// </summary>
        /// <param name="filterKeyword"></param>
        /// <returns></returns>
        public bool ComplyProjectFilter(string filterKeyword)
        {
            if (this._FilterAll) return true;
            if (String.IsNullOrEmpty(filterKeyword)) return false;
            if (filterKeyword == "*") return true;
            return this._FilterDict.ContainsKey(filterKeyword.Trim());
        }
        /// <summary>
        /// Podle počtu vstupních souborů nastaví optimální filtr, typicky po zadání vstupních souborů.
        /// </summary>
        public void PresetOptimalFilters()
        {
            this._InitPresetOptimal();
        }
        /// <summary>
        /// Vrátí optimální preset (jméno) pro soubory odpovídající zadání
        /// </summary>
        /// <param name="fileRange"></param>
        /// <param name="filePath"></param>
        /// <param name="explicitFiles"></param>
        /// <returns></returns>
        public static string GetOptimalPresetName(TraceFileRangeType fileRange, string filePath, List<string> explicitFiles)
        {
            List<string> files = CreateFileList(fileRange, filePath, explicitFiles);
            return GetOptimalPresetName(files.Count);
        }
        /// <summary>
        /// Vrátí optimální preset (jméno) pro daný počet souborů
        /// </summary>
        /// <param name="fileCount"></param>
        /// <returns></returns>
        public static string GetOptimalPresetName(int fileCount)
        {
            if (fileCount <= 3) return PRESET_4;
            if (fileCount <= 7) return PRESET_3;
            if (fileCount <= 12) return PRESET_2;
            if (fileCount <= 18) return PRESET_1;
            return PRESET_0;
        }
        /// <summary>
        /// Vrátí index (0 až nn) daného presetu (název), pokud je neplatný pak vrací -1
        /// </summary>
        /// <param name="presetName"></param>
        /// <returns></returns>
        public static int GetPresetIndex(string presetName)
        {
            return GetPresetIndex(presetName, -1);
        }
        /// <summary>
        /// Vrátí index (0 až nn) daného presetu (název), pokud je neplatný pak vrací defaultValue
        /// </summary>
        /// <param name="presetName"></param>
        /// <returns></returns>
        public static int GetPresetIndex(string presetName, int defaultValue)
        {
            List<string> presets = new List<string>(GetPresetNames());
            int defIndex = presets.FindIndex(p => String.Equals(p, presetName));
            if (defIndex < 0)
                return defaultValue;
            return defIndex;
        }
        /// <summary>
        /// Najde defaultní preset a naplní jej do this
        /// </summary>
        private void _InitPresetDefault()
        {
            _InitPreset(PRESET_DEFAULT);
        }
        /// <summary>
        /// Nastaví preset na filtry optimálně podle množství vstupních souborů
        /// </summary>
        private void _InitPresetOptimal()
        {
            List<string> files = this.CreateCurrentFileList();
            string preset = GetOptimalPresetName(files.Count);
            _InitPreset(preset);
        }
        /// <summary>
        /// Najde daný preset (podle jeho názvu) a naplní jej do this. Typicky se používá konstanta PRESET_???
        /// </summary>
        private void _InitPreset(string defPreset)
        {
            this.PresetDetailLevel = GetPresetIndex(defPreset, 2);
            this.Filter = GetFilterForPreset(defPreset);
        }
        /// <summary>
        /// Uloží do sebe hodnotu filtru, do this._Filter a do this._FilterDict. Vyloučí duplicity. Nekontroluje obsah položek, může být libovolný.
        /// </summary>
        /// <param name="value"></param>
        private void _SetFilter(string value)
        {
            this._Filter = "";
            this._FilterDict = new Dictionary<string,bool>();
            if (!String.IsNullOrEmpty(value))
            {
                string[] fi = value.Split(';');
                foreach (string f in fi)
                {
                    if (!String.IsNullOrEmpty(f))
                    {
                        string filter = f.Trim();
                        if (!this._FilterDict.ContainsKey(filter))
                        {
                            this._FilterDict.Add(filter, true);
                            this._Filter += (this._Filter.Length == 0 ? "" : ";") + filter;
                        }
                    }
                }
            }
            this._FilterAll = this._FilterDict.ContainsKey(FILTER_ALL);
        }
        /// <summary>
        /// Úložiště textové hodnoty filtru ("Filter.Sql;Filter.Instance.RUR;...atd")
        /// </summary>
        private string _Filter;
        /// <summary>
        /// Příznak, že filtr povoluje načítat úplně všechny položky bez omezení
        /// </summary>
        private bool _FilterAll;
        /// <summary>
        /// Index filtračních klíčů. Obsahuje slova, která je povoleno načítat.
        /// </summary>
        private Dictionary<string, bool> _FilterDict;
        /// <summary>
        /// Vrátí sadu názvů presetů pro filtraci.
        /// Jeden preset = jeden název, pod nímž (pomocí metody GetFilterForPreset()) lze získat konkrétní filtrační výraz.
        /// </summary>
        /// <returns></returns>
        public static string[] GetPresetNames()
        {
            string[] result = new string[]
            {
                PRESET_0, PRESET_1, PRESET_2, PRESET_3, PRESET_4
            };
            return result;
        }
        /// <summary>
        /// Pro daný preset vrátí string, který obsahuje přiměřené filtrační výrazy
        /// </summary>
        /// <param name="preset"></param>
        /// <returns></returns>
        public static string GetFilterForPreset(string preset)
        {
            string result = "";
            if (!String.IsNullOrEmpty(preset))
            {
                switch (preset)
                {
                    case PRESET_0:
                        result = FILTER_SQL + ";" +
                                 FILTER_COWLEY + ";" +
                                 FILTER_INSTANCE_RUR + ";" +
                                 FILTER_THREAD + ";" +
                                 FILTER_FORMS_OPEN + ";" +
                                 FILTER_SYSMSG_ERROR;
                        break;
                    case PRESET_1:
                        result = FILTER_SQL + ";" +
                                 FILTER_COWLEY + ";" +
                                 FILTER_INSTANCE_RUR + ";" +
                                 FILTER_THREAD + ";" +
                                 FILTER_FORMS + ";" +
                                 FILTER_FORMS_OPEN + ";" +
                                 FILTER_SYSMSG_ERROR;
                        break;
                    case PRESET_2:   // == PRESET_DEFAULT:
                        result = FILTER_SQL + ";" +
                                 FILTER_COWLEY + ";" +
                                 FILTER_INSTANCE_RUR + ";" +
                                 FILTER_INSTANCE_CHANGE + ";" +
                                 FILTER_INSTANCE_IOM + ";" +
                                 FILTER_THREAD + ";" +
                                 FILTER_FORMS + ";" +
                                 FILTER_FORMS_OPEN + ";" +
                                 FILTER_SYSMSG_ERROR;
                        break;
                    case PRESET_3:
                        result = FILTER_SQL + ";" +
                                 FILTER_COWLEY + ";" +
                                 FILTER_INSTANCE_ALL + ";" +
                                 FILTER_INSTANCE_RUR + ";" +
                                 FILTER_INSTANCE_CHANGE + ";" +
                                 FILTER_INSTANCE_IOM + ";" +
                                 FILTER_THREAD + ";" +
                                 FILTER_FORMS + ";" +
                                 FILTER_FORMS_OPEN + ";" +
                                 FILTER_SYSMSG_ERROR + ";" +
                                 FILTER_SYSMSG_WARNING;
                        break;
                    case PRESET_4:
                        result = FILTER_ALL + ";" +
                                 FILTER_SQL + ";" +
                                 FILTER_COWLEY + ";" +
                                 FILTER_INSTANCE_ALL +";" +
                                 FILTER_INSTANCE_RUR + ";" +
                                 FILTER_INSTANCE_CHANGE + ";" +
                                 FILTER_INSTANCE_IOM +";" +
                                 FILTER_THREAD + ";" +
                                 FILTER_FORMAT +";" +
                                 FILTER_FORMS + ";" +
                                 FILTER_FORMS_OPEN + ";" +
                                 FILTER_CACHE + ";" +
                                 FILTER_SYSMSG_ALL +";" +
                                 FILTER_SYSMSG_ERROR + ";" +
                                 FILTER_SYSMSG_WARNING + ";" +
                                 FILTER_SYSMSG_INFO;
                        break;
                }
            }
            return result;
        }
        public const string PRESET_0 = "Preset.Level.0";
        public const string PRESET_1 = "Preset.Level.1";
        public const string PRESET_2 = "Preset.Level.2";
        public const string PRESET_3 = "Preset.Level.3";
        public const string PRESET_4 = "Preset.Level.4";
        public const string PRESET_DEFAULT = PRESET_2;
        public const string FILTER_ALL = "Filter.All";
        public const string FILTER_SQL = "Filter.Sql";
        public const string FILTER_COWLEY = "Filter.Cowley";
        public const string FILTER_INSTANCE_ALL = "Filter.Instance.All";
        public const string FILTER_INSTANCE_RUR = "Filter.Instance.RUR";
        public const string FILTER_INSTANCE_CHANGE = "Filter.Instance.Change";
        public const string FILTER_INSTANCE_IOM = "Filter.Instance.IOM";
        public const string FILTER_THREAD = "Filter.Thread";
        public const string FILTER_FORMAT = "Filter.Format";
        public const string FILTER_FORMS = "Filter.Forms";
        public const string FILTER_FORMS_OPEN = "Filter.Forms.Open";
        public const string FILTER_CACHE = "Filter.Cache";
        public const string FILTER_SYSMSG_ALL = "Filter.SysMsg.All";
        public const string FILTER_SYSMSG_ERROR = "Filter.SysMsg.Error";
        public const string FILTER_SYSMSG_WARNING = "Filter.SysMsg.Warning";
        public const string FILTER_SYSMSG_INFO = "Filter.SysMsg.Info";
        public const string FILTER_SYSMSG_DEBUG = "Filter.SysMsg.Debug";
        #endregion
        #region OpenFileDialog, SaveAsFileDialog : statické metody, konstanty
        /// <summary>
		/// Zobrazí uživateli dialog pro vyhledání souboru projektu.
		/// Vrací string = jméno souboru k otevření, nebo null = storno.
		/// </summary>
		/// <returns></returns>
		public static string OpenFileDialog()
		{
			string result = null;
			string path = Steward.AppConfig.LastProjectPath;
			string filter = "VisualTrace project (*." + TraceDocument.PROJECT_EXTENSION + ")|*." + TraceDocument.PROJECT_EXTENSION + "|All files (*.*)|*.*";
			bool open = SystemFileDialog.GetExistingFile(ref result, ref path, filter, "." + TraceDocument.PROJECT_EXTENSION, "Otevřít projekt");
			if (open)
			{
				Steward.AppConfig.LastProjectPath = path;
				return result;
			}
			return null;
		}
		/// <summary>
		/// Zobrazí uživateli dialog pro uložení souboru projektu do nového souboru
		/// Vrací string = jméno souboru pro uložení, nebo null = storno.
		/// </summary>
		/// <param name="file"></param>
		/// <returns></returns>
		public static string SaveAsFileDialog(string file)
		{
			string result = (file == null ? "" : file);
			string path = Steward.AppConfig.LastProjectPath;
			string filter = "VisualTrace project (*." + TraceDocument.PROJECT_EXTENSION + ")|*." + TraceDocument.PROJECT_EXTENSION + "|All files (*.*)|*.*";
			bool save = SystemFileDialog.GetOutputFile(ref result, ref path, filter, "." + TraceDocument.PROJECT_EXTENSION, "Uložit projekt jako");
			if (save)
			{
				Steward.AppConfig.LastProjectPath = path;
				return result;
			}
			return null;
		}
        /// <summary>
        /// Metoda vrátí string, který slouží jako filtr pro dialog OpenFile při hledání trace souborů.
        /// Filtr je složen z položek, které vrátí jednotlivé readery.
        /// </summary>
        /// <param name="addFilter">Text filtru, který se přidává na konec. Nemá začínat svislítkem.</param>
        /// <returns></returns>
        public static string GetOpenTraceFilter(string addFilter)
        {
            Dictionary<string, object> filterDict = new Dictionary<string, object>();         // Souhrn filtrů (celé texty)
            Dictionary<string, object> allExtDict = new Dictionary<string, object>();         // Souhrn přípon (jen texty "*.csv", "*.xml")

            IEnumerable<ITraceReader> traceReaders = Support.Steward.GetServiceObjectList<ITraceReader>(true);
            foreach (ITraceReader traceReader in traceReaders)
            {
                string[] filterReader = traceReader.FileOpenFilters;
                if (filterReader != null)
                {
                    foreach (string filter in filterReader)
                    {
                        if (!String.IsNullOrEmpty(filter))
                        {
                            // Celý text filtru:
                            if (!filterDict.ContainsKey(filter))
                                filterDict.Add(filter, null);

                            // Jen přípony:
                            string[] filItm = filter.Split('|');
                            for (int iu = 1; iu < filItm.Length; iu += 2)
                            {   // Projdu prvky 1,3,5,7... = obsahují pouze text přípony
                                string ext = filItm[iu];
                                if (!String.IsNullOrEmpty(ext))
                                {
                                    ext = ext.ToLower();
                                    if (!allExtDict.ContainsKey(ext))
                                        allExtDict.Add(ext, null);
                                }
                            }
                        }
                    }
                }
            }

            string result = "";

            // 1. položka = Všechny trace soubory:
            List<string> extList = new List<string>(allExtDict.Keys);
            extList.Sort();
            string sumExt = "";
            foreach (string ext in extList)
                sumExt += (sumExt.Length == 0 ? "" : ";") + ext;
            if (sumExt.Length > 0)
                result = "All trace files (" + sumExt +")|" + sumExt;
            
            // 2. Další položky = jednotlivé filtry:
            List<string> filterList = new List<string>(filterDict.Keys);
            filterList.Sort();
            foreach (string filter in filterList)
                result += (result.Length == 0 ? "" : "|") + filter;

            // 3. Další explicitní položky:
            if (!String.IsNullOrEmpty(addFilter) && !filterDict.ContainsKey(addFilter))
                result += (result.Length == 0 ? "" : "|") + addFilter;

            return result;

        }
        /// <summary>
        /// "hegtv" : přípona souboru s projektem TraceVisualiseru
        /// </summary>
        public static string PROJECT_EXTENSION = "hegtv";
		#endregion
    }
    #region class TraceDocTextPointer : Popis jednoho konkrétního místa v grafu (čas, filtr, otevřené nody, viditelné nody) + textové informace
    /// <summary>
    /// TraceDocTextPointer : Popis jednoho konkrétního místa v grafu (čas, filtr, otevřené nody, viditelné nody) + textové informace
    /// </summary>
    public class TraceDocTextPointer : TraceDocPointer, ITextGraphPointer
    {
        /// <summary>
        /// Popisek události, krátká informace (první sloupec)
        /// </summary>
        public string Info { get; set; }
        /// <summary>
        /// Popisek události, řádkový text (druhý sloupec)
        /// </summary>
        public string Text { get; set; }
        /// <summary>
        /// Popisek události, odstavcový text
        /// </summary>
        public string TooltipText { get; set; }
    }
    #endregion
    #region class TraceDocPointer : Popis jednoho konkrétního místa v grafu (čas, otevřené nody, viditelné nody)
    /// <summary>
    /// Popis jednoho konkrétního místa v grafu (čas, filtr, otevřené nody, viditelné nody)
    /// </summary>
    public class TraceDocPointer : IGraphPointer
    {
        public TraceDocPointer()
        {
            this.Time = TimeRange.Empty;
            this.ExpandedNodes = new List<string>();
            this.CollapseOtherNodes = true;
        }
        public override string ToString()
        {
            return (this.Name.IsEmpty() ? "(prázdný)" : this.Name);
        }
        /// <summary>
        /// Jméno místa, to se nabízí uživateli
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// Časový interval
        /// </summary>
        public TimeRange Time { get; set; }
		/// <summary>
		/// Řádkový filtr
		/// </summary>
		public RowFilter RowFilter { get; set; }
		/// <summary>
        /// Seznam nodů, které jsou otevřené (ostatní nody jsou zavřené)
        /// </summary>
        public List<string> ExpandedNodes { get; set; }
        /// <summary>
        /// Požadavek na zavření nodů, které nejsou explicitně otevírané (nejsou uvedeny v ExpandedNodes)
        /// </summary>
        public bool CollapseOtherNodes { get; set; }
        /// <summary>
        /// První viditelný node = v prvním řádku
        /// </summary>
        public string FirstVisibleNode { get; set; }
		/// <summary>
		/// První viditelný pixel řádku FirstVisibleNode.
		/// Řádek nemusí být viditelný celý - zde je uveden první pixel (relativní k souřadnici Y = 0 = Top daného řádku), který je vidět.
		/// </summary>
		public int FirstVisiblePixel { get; set; }
        /// <summary>
        /// Selectovaný řádek. Může být empty.
        /// </summary>
        public string SelectedNode { get; set; }
        /// <summary>
		/// Interface předepisuje pro ExpandedNodes typ IEnumerable, a my zde používáme konkrétní typ List.
		/// </summary>
        [PersistingEnabled(false)]
        IEnumerable<string> IGraphPointer.ExpandedNodes { get { return this.ExpandedNodes; } set { this.ExpandedNodes = new List<string>(value); } }
        /// <summary>
        /// Vloží daný node do ExpandedNodes.
        /// Nejprve název node rozloží na jednotlivé cesty a ty vloží postupně od nejvyšší.
        /// </summary>
        /// <param name="selectNode"></param>
        public void AddExpandedNode(string nodeName)
        {
            if (String.IsNullOrEmpty(nodeName)) return;
            if (this.ExpandedNodes == null)
                this.ExpandedNodes = new List<string>();
            char delimiter = '\\';
            string[] names = nodeName.Split(delimiter);
            string current = "";
            foreach (string name in names)
            {
                current += (current.Length == 0 ? "" : delimiter.ToString()) + name;
                this.ExpandedNodes.Add(current);
            }
        }
    }
    #endregion
    #region class TraceServerInfoData : Persistovaná data pro jeden server (nyní jen časové posuny)
    /// <summary>
    /// Persistovaná data o jednom trace serveru
    /// </summary>
    public class TraceServerInfoData
    {
        public TraceServerInfoData(string serverName)
        {
            this.ServerName = serverName;
            this.TimeOffsetList = new List<TraceServerTimeOffset>();
        }
        /// <summary>
        /// Konstruktor pro deserializaci
        /// </summary>
        private TraceServerInfoData() 
        {
            this.TimeOffsetList = new List<TraceServerTimeOffset>();
        }
        /// <summary>
        /// Název serveru
        /// </summary>
        public string ServerName { get; private set; }
        /// <summary>
        /// Soupis časových posuvů
        /// </summary>
        internal List<TraceServerTimeOffset> TimeOffsetList { get; private set; }
        /// <summary>
        /// Obsahuje true, pokud v tomto objektu existují data vhodná k persistenci
        /// </summary>
        public bool ContainData { get { return this.TimeOffsetList.Any(o => o.Offset != 0D); } }
    }
    /// <summary>
    /// Persistovaná data o jednom časovém posunu
    /// </summary>
    public class TraceServerTimeOffset
    {
        internal TraceServerTimeOffset(int key, double offset)
        {
            this.Key = key;
            this.Offset = offset;
        }
        /// <summary>
        /// Konstruktor pro deserializaci
        /// </summary>
        private TraceServerTimeOffset() { }
        /// <summary>
        /// Klíč času, pro který zde evidujeme offset
        /// </summary>
        public int Key { get; private set; }
        /// <summary>
        /// Počet sekund časového offsetu
        /// </summary>
        public double Offset { get; internal set; }
        /// <summary>
        /// Vrátí počet hodin, které uplynuly od času T0 do daného okamžiku.
        /// Vrací pouze celé číslo.
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static int TimeToKey(DateTime time)
        {
            TimeSpan timeSpan = time - new DateTime(2012, 01, 01);
            double key = timeSpan.TotalHours * 4d;                   // Klíč: jednotka odpovídá čtvrthodině
            return (int)(Math.Round(key, 0));                        // Round na celá čísla.
        }
    }
    #endregion
    #region class DocumentGraphLayout : Layout grafu, je součástí dokumentu a ukládá se s ním
    /// <summary>
    /// Layout grafu, je součástí dokumentu a ukládá se s ním
    /// </summary>
    internal class DocumentGraphLayout : IGraphLayout
    {
        public DocumentGraphLayout()
        {
            this.WidthList = new List<int>();
        }
        public DocumentGraphLayout(bool fillDefault)
        {
            this.WidthList = new List<int>();
            this.HeaderTop = 0;
            this.HeaderHeight = 44;
            this.HeaderFilterHeight = 18;
            this.WidthList.Add(24);
            this.WidthList.Add(185);
        }
        #region IGraphLayout Members
        public int HeaderTop { get; set;}
        public int HeaderHeight{ get; set;}
        public int HeaderFilterHeight{ get; set;}
        public List<int> WidthList{ get; set;}
        #endregion
    }
    #endregion
    #region enum TraceFileRangeType
    /// <summary>
    /// Režim trace souborů
    /// </summary>
    public enum TraceFileRangeType
    {
        /// <summary>
        /// Neurčen
        /// </summary>
        None = 0,
        /// <summary>
        /// Jeden soubor
        /// </summary>
        OneFile = 1,
        /// <summary>
        /// Obsah jednoho adresáře
        /// </summary>
        OneDirectory = 2,
        /// <summary>
        /// Obsah jednoho adresáře a jeho podadresářů
        /// </summary>
        AllSubdirectories = 3,
        /// <summary>
        /// Jednotlivě vybrané soubory
        /// </summary>
        ExplicitFiles = 4
    }
    #endregion
}
